package com.arcticcoding.telemetrie;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Observable;

import android.content.Context;
import android.util.Log;

import com.arcticcoding.telemetrie.comm.TelemetrieResponse;

public class TelemetrieProvider extends Observable {
	
	private static final String TAG = "com.arcticcoding.telemetrie.TelemetrieProvider";
	private final Context 		applicationContext;
	
	//TelemetrieService names
	public static final String CAMERA_SERVICE 	= "com.arcticcoding.CameraService";
	public static final String GEO_SERVICE		= "com.arcticcoding.GeoService";
	
	//Map of services and their names
	private final Map<String, Class<? extends TelemetrieService>> serviceMap 
		= new HashMap<String, Class<? extends TelemetrieService>>();
	List<TelemetrieService> runningServices;
	
	public TelemetrieProvider(Context context) {
		//Do that init stuff
		runningServices = new LinkedList<TelemetrieService>();
		applicationContext = context;
		serviceMap.put(CAMERA_SERVICE, CameraService.class);
		serviceMap.put(GEO_SERVICE, GeoService.class);
		startService(CAMERA_SERVICE);
		
		Log.d(TAG, runningServices.toString());
	}
	
	public void setTelemetrieCallback(TelemetrieCallback callback) {
		if(callback == null)
			throw new IllegalArgumentException();
		
		addObserver(callback);
	}
	
	/**
	 * This method is called by a TelemetrieService if it values
	 * has changed.
	 * This will notifie all callbacks.
	 */
	protected synchronized void valueHasChanged(TelemetrieResponse value) {
		Log.d(TAG, "Value has changed");
		setChanged();
		notifyObservers(value);
	}
	
	/**
	 * This will start a new TelemetrieService of the specified class.
	 * 
	 * @param service The service which should be started.
	 */
	public boolean startService(Class<? extends TelemetrieService> service) {
		TelemetrieService newInstance = null;
		Log.d(TAG, "Start");
		try {
			newInstance = (service.getConstructor()).newInstance();
		} catch(Exception e) {
			Log.d(TAG, "Can't instantiate null instance");
			e.printStackTrace();
		}

		if (newInstance != null) {
			if (runningServices.contains(newInstance)) {
				Log.d(TAG,
						"Service already running: "
								+ service.getCanonicalName());
				return false;
			} else {
				Constructor<? extends TelemetrieService> constructor;
				TelemetrieService instance = null;
				Log.d(TAG, "Start adding the Service");
				try {
					constructor = service.getConstructor(TelemetrieProvider.class);
					if(constructor == null)
						Log.d(TAG, "No constructor");
					instance = constructor.newInstance(this);
				} catch(InvocationTargetException i) {
					i.getCause().printStackTrace();
				} catch(Exception e) {
					e.printStackTrace();
					Log.d(TAG, "Exception");
				}
				
				if(instance != null) {
					runningServices.add(instance);
					Log.d(TAG, "Service started: " + service.getCanonicalName());
				} else {
					Log.d(TAG, "Instance not started");
				}
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Starts the service specified by the name.
	 * This calls the startService(Class) method.
	 * 
	 * @param serviceName The name of the service. See public Strings of this class.
	 * @return true if a new instance was started, false if not.
	 */
	public boolean startService(String serviceName) {
		if(serviceMap.containsKey(serviceName)) {
			return startService(serviceMap.get(serviceName));
		} else {
			return false;
		}
	}
	
	/**
	 * This will stop all running instances of the specified service.
	 * 
	 * @param service The service which should be stopped.
	 */
	public boolean stopService(Class<? extends TelemetrieService> service) {
		if(serviceMap.containsValue(service)) {
			TelemetrieService tmpService;
			
			try {
				tmpService = service.newInstance();
				if(runningServices.contains(tmpService)) {
					tmpService = runningServices.get(
							runningServices.indexOf(tmpService));
					tmpService.shutdown();
					tmpService = null;
					return true;
				} else { 
					return false;
				}
			} catch(Exception e) {
				Log.d(TAG, "Cant initiate new instance.");
				return false;
			}
		} else {
			return false;
		}
	}
	
	/**
	 * 
	 * @param serviceName
	 */
	public boolean stopService(String serviceName) {
		if(serviceMap.containsKey(serviceName)) {
			return stopService(serviceMap.get(serviceName));
		} else {
			return false;
		}
	}
	
	/**
	 * This will return a list holding the name of all running services
	 * 
	 * @return
	 */
	public List<String> getRunningServices() {
		List<String> serviceNames = new LinkedList<String>();
		
		for(TelemetrieService service: runningServices) {
			serviceNames.add(service.toString());
		}
		
		return serviceNames;
	}
	
	/**
	 * Return the instance of the running service.
	 * 
	 * @param serviceName The name of the serice.
	 * @return The instance of the running service, if there isn't any running instance or there is no 
	 * 		service with this name it will return null.
	 */
	public TelemetrieService getService(String serviceName) {
		if(serviceMap.containsKey(serviceName)) {
			Log.d(TAG, "Map contains string");
			
			TelemetrieService tmpService = null;
			
			try {
				Class<? extends TelemetrieService> c = serviceMap.get(serviceName);
				if(c != null) {
					Log.d(TAG, "Creating c");
					tmpService = c.newInstance(); //TODO Fuck that shit #EDIT# Seems to work out of the main thread
					Log.d(TAG, "C created");
				}
				
				Log.d(TAG, "tmpService: " + tmpService.toString());
				
				if(runningServices.contains(tmpService)) {
					Log.d(TAG, "Should now return");
					return runningServices.get(runningServices.indexOf(tmpService));
				} else {
					Log.d(TAG, "getService: Return null");
					return null;
				}
			} catch(Exception e) {
				Log.d(TAG, "Cant initialize a new instance.");
				e.printStackTrace();
				return null;
			} finally {
				if(tmpService != null)
					tmpService.shutdown();
			}
		} else {
			return null;
		}
	}
	
	/**
	 * Return the ApplicationContext
	 * 
	 * @return The ApplicationContext
	 */
	protected Context getApplicationContext() {
		return applicationContext;
	}
	
	/**
	 * Stops the TelemetrieProvider and all TelemetrieServices
	 * 
	 */
	public void stop() {
		for(TelemetrieService service: runningServices) {
			//Do dat shutdown stuff
			service.shutdown();
		}
	}

}
